Suicide By Micro-Stub
Imagine you have successfully injected a payload into a remote process. Your code executes, does its job perfectly, and now it's time to pack up and leave. But we like to do things stealthily, to maintain OPSEC and avoid leaving Indicators of Compromise like a massive chunk of unbacked PAGE_EXECUTE_READWRITE memory sitting around. The logical step is to have our payload free its own memory. In this blog we will see how to pull the rug underneath our legs without tripping over.
Setup
Everything which we are going to talk about is done on latest Windows and defender versions, which at the time of writing this blog are -
Windows OS
- Edition: Windows 11 Pro
- Version:
25H2 - OS Build:
26200.7840
Defender Engine
- Client:
4.18.26010.5 - Engine:
1.1.26010.1 - AV / AS:
1.445.222.0
Environment
Everything is created and built to test modern security with all security feature turned ON:
✓ Real-time protection
✓ Tamper Protection
✓ Memory integrity
✓ Memory access protection
✓ Microsoft Vulnerable Driver Blocklist
This is not just any project built to run in a vulnerable environment with security features turned off. This is some serious work and hence made just for education and research purposes.
Returning to the Void
If we want to remove our payload from memory, the standard way is to call VirtualFree (or its native counterpart, NtFreeVirtualMemory). At a high level, this seems fine. The API executes, the kernel unmaps our memory page, and our tracks are wiped.
But we are ignoring the fundamental mechanics of how x64 assembly handles function calls. Let's look at a fatal example:
; Assuming RCX, RDX, R8, R9 are set up for NtFreeVirtualMemory
call r15 ; Call NtFreeVirtualMemory
; --- WE NEVER REACH HERE ---
mov rcx, 0
call RtlExitUserThread
This looks logically perfect but crash the host process? Let's debug:
- The CALL Instruction
When we execute call r15, the CPU pushes the address of the next instruction (our mov rcx, 0) onto the stack. This is the return address.
- The API Executes
NtFreeVirtualMemory does its job perfectly. The entire memory region containing our payload is unmapped and returned to the OS. That return address sitting safely on our stack is now a ghost pointer, it points to a location that no longer exists.
- The RET Instruction
NtFreeVirtualMemory finishes executing and hits its internal ret instruction.
- The Fatal Pop
The CPU pops the saved return address off the stack and loads it directly into the Instruction Pointer (RIP).
- THE CRASH
The CPU attempts to fetch the next instruction from RIP. But RIP is now pointing to a memory page that literally no longer exists!. The CPU is trying to execute code from the void, resulting in an immediate, crash.
Ok enough theory, lets look at it in action..

The Setup
In the above image we can see the CPU setting up the parameters for ZwFreeVirtualMemory

The Crash